package com.googlecode.serialization;

import java.util.Date;

public class SerializingTranscoder extends AbsSerializingTranscoder implements Transcoder<Object> {

	// General flags
	public static final int SERIALIZED = 1;
	public static final int COMPRESSED = 2;

	// Special flags for specially handled types.
	public static final int SPECIAL_MASK = 0xff00;
	public static final int SPECIAL_BOOLEAN = (1 << 8);
	public static final int SPECIAL_INT = (2 << 8);
	public static final int SPECIAL_LONG = (3 << 8);
	public static final int SPECIAL_DATE = (4 << 8);
	public static final int SPECIAL_BYTE = (5 << 8);
	public static final int SPECIAL_FLOAT = (6 << 8);
	public static final int SPECIAL_DOUBLE = (7 << 8);
	public static final int SPECIAL_BYTEARRAY = (8 << 8);

	private final int maxSize;
	private boolean primitiveAsString;
	private final TranscoderHelper transcoderHelper = new TranscoderHelper(true);
	
	/**
	 * Get a serializing transcoder with the default max data size.
	 */
	public SerializingTranscoder() {
		this(DataInfo.MAX_SIZE);
	}

	/**
	 * Get a serializing transcoder that specifies the max data size.
	 */
	public SerializingTranscoder(int max) {
		this.maxSize = max;
	}

	public final int getMaxSize() {
		return this.maxSize;
	}

	@Override
	public Object decode(DataInfo d) {
		byte[] data = d.getData();

		int flags = d.getFlag();
		if ((flags & COMPRESSED) != 0) {
			data = uncompress(d.getData());
		}
		flags = flags & SPECIAL_MASK;
		return decode0(d, data, flags);
	}

	protected final Object decode0(DataInfo cachedData, byte[] data, int flags) {
		Object rv = null;
		if ((cachedData.getFlag() & SERIALIZED) != 0 && data != null) {
			rv = deserialize(data);
		} else {
			if (this.primitiveAsString) {
				if (flags == 0) {
					return decodeString(data);
				}
			}
			if (flags != 0 && data != null) {
				switch (flags) {
				case SPECIAL_BOOLEAN:
					rv = Boolean.valueOf(this.transcoderHelper.decodeBoolean(data));
					break;
				case SPECIAL_INT:
					rv = Integer.valueOf(this.transcoderHelper.decodeInt(data));
					break;
				case SPECIAL_LONG:
					rv = Long.valueOf(this.transcoderHelper.decodeLong(data));
					break;
				case SPECIAL_BYTE:
					rv = Byte.valueOf(this.transcoderHelper.decodeByte(data));
					break;
				case SPECIAL_FLOAT:
					rv = new Float(Float.intBitsToFloat(this.transcoderHelper.decodeInt(data)));
					break;
				case SPECIAL_DOUBLE:
					rv = new Double(Double.longBitsToDouble(this.transcoderHelper.decodeLong(data)));
					break;
				case SPECIAL_DATE:
					rv = new Date(this.transcoderHelper.decodeLong(data));
					break;
				case SPECIAL_BYTEARRAY:
					rv = data;
					break;
				default:
					logger.warn(String.format("Undecodeable with flags %x", flags));
				}
			} else {
				rv = decodeString(data);
			}
		}
		return rv;
	}

	@Override
	public DataInfo encode(Object o) {
		byte[] b = null;
		int flags = 0;
		if (o instanceof String) {
			b = encodeString((String) o);
		} else if (o instanceof Long) {
			b = (this.primitiveAsString) ? encodeString(o.toString()) : this.transcoderHelper.encodeLong((Long) o);
			flags |= SPECIAL_LONG;
		} else if (o instanceof Integer) {
			b = (this.primitiveAsString) ? encodeString(o.toString()) : this.transcoderHelper.encodeInt((Integer) o);
			flags |= SPECIAL_INT;
		} else if (o instanceof Boolean) {
			b = (this.primitiveAsString) ? encodeString(o.toString()) : this.transcoderHelper
					.encodeBoolean((Boolean) o);
			flags |= SPECIAL_BOOLEAN;
		} else if (o instanceof Byte) {
			b = (this.primitiveAsString) ? encodeString(o.toString()) : this.transcoderHelper.encodeByte((Byte) o);
			flags |= SPECIAL_BYTE;
		} else if (o instanceof Float) {
			b = (this.primitiveAsString) ? encodeString(o.toString()) : this.transcoderHelper.encodeInt(Float
					.floatToRawIntBits((Float) o));
			flags |= SPECIAL_FLOAT;
		} else if (o instanceof Double) {
			b = (this.primitiveAsString) ? encodeString(o.toString()) : this.transcoderHelper.encodeLong(Double
					.doubleToRawLongBits((Double) o));
			flags |= SPECIAL_DOUBLE;
		} else if (o instanceof Date) {
			b = this.transcoderHelper.encodeLong(((Date) o).getTime());
			flags |= SPECIAL_DATE;
		} else if (o instanceof byte[]) {
			b = (byte[]) o;
			flags |= SPECIAL_BYTEARRAY;
		} else {
			b = serialize(o);
			flags |= SERIALIZED;
		}
		assert b != null;
		if (this.primitiveAsString) {
			// It is not be SERIALIZED,so change it to string type
			if ((flags & SERIALIZED) == 0) {
				flags = 0;
			}
		}
		// 是否大于压缩上限，如果大于，进行压缩
		if (b.length > this.compressionThreshold) {
			byte[] compressed = compress(b);
			if (compressed.length < b.length) {
				if (logger.isDebugEnabled()) {
					logger.debug(String.format("Compressed %s from %d to %d ", o.getClass().getName(), b.length,
							compressed.length));
				}
				b = compressed;
				flags |= COMPRESSED;
			} else {
				if (logger.isDebugEnabled()) {
					logger.debug(String.format("Compression increased the size of %s from %d to %d", o.getClass().getName(), 
							b.length, compressed.length));
				}
			}
		}
		return new DataInfo(flags, b, this.maxSize, -1);
	}

	@Override
	public boolean isPackZeros() {
		return this.transcoderHelper.isPackZeros();
	}

	@Override
	public boolean isPrimitiveAsString() {
		return this.primitiveAsString;
	}

	@Override
	public void setPackZeros(boolean packZeros) {
		this.transcoderHelper.setPackZeros(packZeros);
	}

	@Override
	public void setPrimitiveAsString(boolean primitiveAsString) {
		this.primitiveAsString = primitiveAsString;
	}
	
	public TranscoderHelper getTranscoderHelper() {
		return transcoderHelper;
	}

}
